/*
 * Description: Simple character device driver for NIC (RTL8136)
 * Build: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 
 * Note: Runs on Linux Kernel 2.4 or 2.6 and tested on Ubuntu 11.04 running on Lenovo-Z560 Notebook
 * Author: Vijay Chavan <VijayChavan007@gmail.com>	
*/

#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Vijay Chavan <VijayChavan007@gmail.com>");
MODULE_DESCRIPTION("Simple Character device driver for Realtek NIC to read MAC address and Link Status");

#define USE_PCI_PROBE

/* PCI configuration space for rtl8169 based NIC*/

#define VENDOR_ID		0x10EC
#define DEVICE_ID         	0x8136

#define PCI_IO_OFFSET     	0x10
#define PCI_MEMORY_OFFSET 	0x14
#define PCI_IRQ_OFFSET    	0x3C

/* rtl8139 registers */

#define PHY_STATUS_OFFSET	0x6C

#define BIT_1			(1 << 1)
#define LINK_STS(PHYStatus)	(PHYStatus & BIT_1) ? 1:0

#define CDEV_ETH_MAJOR		250
#define CDEV_ETH_NAME		"cdev_eth"

unsigned long ulIOBase, ulIOBaseEnd;
unsigned long ulMemoryBase, ulMemoryBaseEnd;
unsigned long ulResFlags;
unsigned char cIntLine;
unsigned char devaddr[6];

/********************************************************/
/*		Read MAC address			*/
/********************************************************/
void GetMACAddr(void)
{
    int i;
    unsigned char *ptr;

    /* read from device memory */
    printk("%s: MAC Address (From Memory): ", CDEV_ETH_NAME);
    ptr = ioremap(ulMemoryBase, 256);
    for (i = 0; i < 6; i++)
    {
	devaddr[i] = readb(ptr+i);
	printk("%02x", devaddr[i]);
	if(i < 5)
	printk(":");
    }
    printk("\n");
    iounmap(ptr);
}

/********************************************************/
/*	Read Ethernet link Connection Status		*/
/********************************************************/
static unsigned char GetLinkStatus(void)
{
    unsigned char status;
    unsigned char *ptr;

    /* read from device memory */
    ptr = ioremap(ulMemoryBase, 256);
    status = LINK_STS(readb(ptr+PHY_STATUS_OFFSET));
    printk("%s: Link Connected: %d\n", CDEV_ETH_NAME, status);
    iounmap(ptr);
    return status;
}

/********************************************************/
/*	read the configuration space and extract BARs	*/
/********************************************************/
static int get_card_details(struct pci_dev *pdev)
{
    printk("%s: *** PCI Device Registers ***\n", CDEV_ETH_NAME);

    ulMemoryBase = pci_resource_start( (struct pci_dev *)pdev, 2);

    ulMemoryBaseEnd = pci_resource_end( (struct pci_dev *)pdev, 2);

    ulResFlags = pci_resource_flags( (struct pci_dev *)pdev, 2);

    if (ulResFlags & IORESOURCE_MEM)
      printk("%s: Memory Mapped Base Address is: 0x%08lx End 0x%08lx\n", 
		CDEV_ETH_NAME, ulMemoryBase, ulMemoryBaseEnd);

    ulIOBase = pci_resource_start( (struct pci_dev *)pdev, 0);

    ulIOBaseEnd = pci_resource_end( (struct pci_dev *)pdev, 0);

    ulResFlags = pci_resource_flags( (struct pci_dev *)pdev, 0);

    if (ulResFlags & IORESOURCE_IO)
      printk("%s: I/O Mapped Base Address is: 0x%08lx End 0x%08lx\n", 
		CDEV_ETH_NAME, ulIOBase, ulIOBaseEnd);

    pci_read_config_byte( (struct pci_dev *)pdev,
				PCI_INTERRUPT_LINE, &cIntLine);
    printk("%s: IRQ Used is: 0x%02x \n", CDEV_ETH_NAME,cIntLine);
    return 0;
}

/************************************************************************************
****	When application calls int open(const char *pathname, int flags),	 ****
****	cdev_eth_open() method will be invoked by the Kernel, which returns		 ****
****	the file descriptor for the device or -1 if any error occurs		 ****
************************************************************************************/

static int cdev_eth_open(struct inode *inode, struct file *filp)
{
      int minor;
      minor = MINOR(filp->f_dentry->d_inode->i_rdev);
      printk("%s: ***** Open at minor %d *****\n",CDEV_ETH_NAME, minor);
      return 0;
}

/************************************************************************************
****	When application calls int close(int fd), where fd is the file		 ****
****	descriptor returned by corresponding open(),				 ****
****	cdev_eth_close() method will be invoked by the Kernel, which returns		 ****
****	0 on success and -1 on error						 ****
************************************************************************************/

static int cdev_eth_close(struct inode *inode, struct file *filp)
{
    int minor;
    minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    printk("%s: ***** Close at minor %d *****\n",CDEV_ETH_NAME, minor);
    return 0;
}


/************************************************************************************
****	When application calls ssize_t read(int fd, void *buf, size_t count),	 ****
****	where fd is the file descriptor returned by corresponding open(),	 ****
****	buf is user buffer, and count is the number of bytes requested,		 ****
****	cdev_eth_read() method will be invoked by the Kernel, which returns	 ****
****	number of bytes actually transferred or -1 on error			 ****
************************************************************************************/


static ssize_t cdev_eth_read(struct file *filp, char *buf, size_t count, loff_t *offset)
{
    int minor;
    minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    printk("%s: ***** Read at minor %d *****\n", CDEV_ETH_NAME, minor);
    copy_to_user(buf, devaddr, min(count, (size_t)6));
    return count;
}

/************************************************************************************
****	When application calls ssize_t write(int fd, const void *buf, size_t count),*
****	where fd is the file descriptor returned by corresponding open(),	 ****
****	buf is user buffer with data, and count is the number of bytes to be written,
****	cdev_eth_write() method will be invoked by the Kernel, which returns	 ****
****	number of bytes actually transferred or -1 on error			 ****
************************************************************************************/

static ssize_t cdev_eth_write(struct file *filp, const char *buf, size_t count, loff_t *offset)
{
    int minor;
    minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    printk("%s: ***** Write at minor %d *****\n",CDEV_ETH_NAME, minor);
    return count;
}

/************************************************************************************
****	When application calls int ioctl(int fd, int request char *argp),        ****
****	where fd is the file descriptor returned by corresponding open(),	 ****
****	request is command, and argp is a pointer to argument list,              ***
****	cdev_eth_ioctl() method will be invoked by the Kernel, which returns	 ****
****	0 or a positive integer on success or -1 on error			 ****
************************************************************************************/
static long cdev_eth_ioctl(struct file *filp, unsigned int command, unsigned long argument)
{
    int minor;
    char *buf;
    char status;
    minor = MINOR(filp->f_dentry->d_inode->i_rdev);
    printk("%s: ***** IOCTL at minor %d *****\n", CDEV_ETH_NAME, minor);
    status = GetLinkStatus();
    buf = (char *) argument;
    copy_to_user(buf, &status, 1);
    return 0;
}

/****************************************/
/*	    File Operations		*/
/****************************************/
struct file_operations cdev_eth_fops = {
        read    	:       cdev_eth_read,
        write   	:       cdev_eth_write,
        open    	:       cdev_eth_open,
        release 	:       cdev_eth_close,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
        unlocked_ioctl 	:       cdev_eth_ioctl,
#else
	ioctl		:	cdev_eth_ioctl,
#endif
        owner   	:       THIS_MODULE,
        };

/****************************************/
/*	    PCI Probe Function		*/
/****************************************/
static int cdev_eth_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
  int res = 0;
  printk("%s: *** PCI probe ***\n", CDEV_ETH_NAME);

  register_chrdev(CDEV_ETH_MAJOR,"cdev_eth",&cdev_eth_fops);
  res = get_card_details(pdev);

  if (res == 0)
  {
    GetMACAddr();
  }
    return 0;
}

/****************************************/
/*	    PCI Remove Function		*/
/****************************************/
static void cdev_eth_remove(struct pci_dev *pdev)
{
    printk("%s: *** PCI remove ***\n", CDEV_ETH_NAME);
}


static struct pci_device_id cdev_eth_pci_tbl[] = {
	{VENDOR_ID, DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
	{0}
};

struct pci_driver cdev_eth_driver = {
	name: 		CDEV_ETH_NAME,
	id_table: 	cdev_eth_pci_tbl,
	probe: 		cdev_eth_probe,
	remove: 	cdev_eth_remove,
};

/****************************************/
/*	    Initialization		*/
/****************************************/
static int __init cdev_eth_init(void)
{
#ifndef USE_PCI_PROBE
  struct pci_dev *pdev = NULL;
#endif
  printk("\n\n%s: *** Module Initialized ***\n", CDEV_ETH_NAME);

#ifdef USE_PCI_PROBE

  #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
	return pci_register_driver(&cdev_eth_driver);
  #else
	return pci_module_init(&cdev_eth_driver);
  #endif

#else
  pdev = pci_get_device(VENDOR_ID,DEVICE_ID,0);
  if (pdev == 0)
  {
     printk("%s: *** Device not found ***\n", CDEV_ETH_NAME);
     return -1;
  }
  else
  {
    printk("%s: *** Device found ***\n", CDEV_ETH_NAME);
    cdev_eth_probe(pdev, NULL);
    return 0;
  }
#endif
}

/****************************************/
/*	    	Cleanup			*/
/****************************************/
static void __exit cdev_eth_exit(void)
{
  printk("%s: *** Module removed ***\n", CDEV_ETH_NAME);
#ifdef USE_PCI_PROBE
  pci_unregister_driver(&cdev_eth_driver);
#endif
  unregister_chrdev(CDEV_ETH_MAJOR,"cdev_eth");
}

module_init(cdev_eth_init);
module_exit(cdev_eth_exit);

